package jmine.tec.web.front.pages.form;

import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.ERROR_ON_FORM_VALIDATION;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.INFO_ENTITY_SAVE_SUCCESS;
import static jmine.tec.web.wicket.pages.form.WebFormPagesMessages.LABEL_FORM_TITLE;
import static jmine.tec.web.wicket.spring.SpringBeanReference.forName;

import java.lang.reflect.ParameterizedType;

import jmine.tec.extensions.forms.FormHandler;
import jmine.tec.persist.PersistMessages;
import jmine.tec.utils.reflection.GenericTypeResolverImpl;
import jmine.tec.web.front.forms.FormMetadataRegistry;
import jmine.tec.web.wicket.BeanReference;
import jmine.tec.web.wicket.component.button.ActionSubmitLink;
import jmine.tec.web.wicket.exception.ExceptionHandler;
import jmine.tec.web.wicket.pages.form.AbstractFormPage;
import jmine.tec.web.wicket.pages.form.FormType;
import jmine.tec.web.wicket.spring.SpringBeanReference;

import org.apache.wicket.Page;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.SubmitLink;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;

import bancosys.tec.exception.BusinessException;
import bancosys.tec.persist.exception.PersistenceException;

/**
 * Page to handle webfront forms entities
 * 
 * @author israel.crisanto
 * @param <BO> Form type to handle
 */
public abstract class HandledFormPage<BO> extends AbstractFormPage {

    private Form<BO> form;

    private FormType formType;

    private WebMarkupContainer fieldset;

    private BO model;

    private FormHandler<BO> formHandler;

    private final Class<? extends Page> sourcePage;

    private BeanReference<ExceptionHandler> exceptionHandler = forName("wicketExceptionHandler");

    private IModel<BO> propertyModel;

    /**
     * Constructor for new forms used by menu calls
     * 
     * @param pageParameters page parameters
     */
    public HandledFormPage(PageParameters pageParameters) {
        super(pageParameters);

        this.formType = FormType.NEW;
        this.sourcePage = this.getClass();
        this.model = this.createModel();
        this.formHandler = this.createHandler();
        this.createComponents();
    }

    /**
     * Constructor for new forms with a parent page to return if needed
     * 
     * @param sourcePage parent page
     * @param pageParameters page parameters
     */
    public HandledFormPage(Class<? extends Page> sourcePage, PageParameters pageParameters) {
        super(pageParameters);

        this.formType = FormType.NEW;
        this.sourcePage = sourcePage;
        this.model = this.createModel();
        this.formHandler = this.createHandler();
        this.createComponents();
    }

    /**
     * Constructor
     * 
     * @param sourcePage parent page
     * @param pageParameters page parameters
     * @param entity entity model
     * @param formType type of form
     */
    public HandledFormPage(Class<? extends Page> sourcePage, PageParameters pageParameters, BO entity, FormType formType) {
        super(pageParameters);

        this.formType = formType;
        this.sourcePage = sourcePage;
        this.model = entity;
        this.formHandler = this.createHandler();
        this.createComponents();
    }

    /**
     * Override this method to change the way the model is created
     * 
     * @return object model
     */
    protected BO createModel() {
        BO entity = null;

        try {
            entity = this.getFormClass().newInstance();
        } catch (Exception e) {
            this.exceptionHandler.get().handle(e);
        }

        return entity;
    }

    /**
     * Override to do some pre-handler action or validation
     * 
     * @param entity entity to verify
     * @return error description if some
     */
    protected String beforeApplyHandle(BO entity) {
        return "";
    }

    /**
     * Override to change the way the handler is created
     * 
     * @return form handler
     */
    protected FormHandler<BO> createHandler() {
        SpringBeanReference<FormHandler<BO>> entityHandler = SpringBeanReference.forName("formHandler");
        return entityHandler.get();
    }

    /**
     * Create page components
     */
    private void createComponents() {
        this.propertyModel = new CompoundPropertyModel<BO>(this.model);
        this.createFeedbackPanel();
        this.createFormComponents();
        this.renderFormPanel();
    }

    /**
     * Build form components
     */
    private void createFormComponents() {
        this.createMainForm();
        this.createPanelTitle();
        this.createFieldSet();
        this.createFormButtons();
    }

    /**
     * Create main form markup
     */
    private void createMainForm() {
        this.form = new Form<BO>("mainForm", this.getPropertyModel());
    }

    /**
     * Create our form model
     * 
     * @return wicket form model
     */
    protected IModel<BO> getPropertyModel() {
        return this.propertyModel;
    }

    /**
     * Create form buttons
     */
    private void createFormButtons() {
        this.createSubmitButton();
        this.createBackButton();
    }

    /**
     * Create our form inner fieldset
     */
    private void createFieldSet() {
        this.fieldset = new WebMarkupContainer("mainFieldset");
        this.form.add(this.fieldset);
    }

    /**
     * Create and render our form respective panel
     */
    private void renderFormPanel() {
        HandledFormPanelFactory factory = this.loadFormPanelFactory();
        this.fieldset.add(factory.createPanel("panel", this.getPropertyModel(), FormType.NEW));
    }

    /**
     * Create a back button
     */
    private void createBackButton() {
        this.form.add(new Link<Object>("btnVoltar") {
            @Override
            public void onClick() {
                this.setResponsePage(HandledFormPage.this.sourcePage, HandledFormPage.this.getPageParameters());
            }
        });
        this.add(this.form);
    }

    /**
     * Create a submit button
     */
    private void createSubmitButton() {
        SubmitLink submit = new ActionSubmitLink("btnGravar") {
            @Override
            public void onAction() {
                HandledFormPage.this.sendtoHandler();
            }
        };
        submit.setVisible(!this.formType.isView());
        this.form.add(submit);
    }

    /**
     * Create the form title
     */
    private void createPanelTitle() {
        if (this.formType.isView()) {
            this.add(new Label("formTitle", this.getPageName()));
        } else {
            this.add(new Label("formTitle", LABEL_FORM_TITLE.create(this.getPageName()).getMessage()));
        }
    }

    /**
     * Feedback panel
     */
    private void createFeedbackPanel() {
        this.add(new FeedbackPanel("feedback"));
    }

    /**
     * Default action on form submit
     */
    private void sendtoHandler() {
        BO target = this.form.getModelObject();
        String validationErrors = this.beforeApplyHandle(target);

        if (validationErrors.length() > 0) {
            throw new BusinessException(ERROR_ON_FORM_VALIDATION.create(validationErrors));
        }

        this.formHandler.apply(target);
        this.getSession().info(INFO_ENTITY_SAVE_SUCCESS.create().getMessage());
    }

    /**
     * Return current form class
     * 
     * @return form class
     */
    @SuppressWarnings("unchecked")
    private Class<BO> getFormClass() {
        try {
            return (Class<BO>) GenericTypeResolverImpl.getInstance().resolveTypeForParameterizedType(
                    (ParameterizedType) this.getClass().getGenericSuperclass(), this.getClass(), 0);
        } catch (IllegalArgumentException e) {
            throw new PersistenceException(PersistMessages.INVALID_BEAN_IMPLEMENTATION.create(this.getClass()));
        }
    }

    /**
     * Get form panel factory from metadata
     * 
     * @return panel factory
     */
    private HandledFormPanelFactory loadFormPanelFactory() {
        BeanReference<FormMetadataRegistry> requestMetadata = forName("formMetadataRegistry");
        return requestMetadata.get().find(this.model).getFormPanelFactory();
    }
}
